<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>2048 Game</title>
    <style>
        * {
            padding: 0;
            margin: 0;
        }
    </style>
    <!-- OSC挂件 -->
    <script src='http://git.oschina.net/youngster/game2048/widget_preview'></script>
    <style>
        .pro_name a{color: #4183c4;}
        .osc_git_title{background-color: #d8e5f1;}
        .osc_git_box{ width: 280px; position: absolute; right: 20px; top: 20px; background-color: #fafafa;}
        .osc_git_box{border-color: #ddd;}
        .osc_git_info{color: #666;}
        .osc_git_main a{color: #4183c4;}
    </style>
</head>
<body>

<canvas id="canvas"></canvas>
<script src="bower_components/jquery/dist/jquery.min.js"></script>
<script src="js/engine.js"></script>
<script>
    $(function () {
        var canvas = $("#canvas");

        // set canvas width and height
        var changeCanvasSize = function () {
            canvas.attr("width", window.innerWidth);
            canvas.attr("height", window.innerHeight - 5);
        };
        changeCanvasSize();
        $(window).resize(function () {
            changeCanvasSize();
        });

        /**
         * @param spec.width default 107
         * @param spec.height default 107
         * @param spec.radius default 6
         * @param spec.color default #eee4da
         * @param spec.alpha default 1
         */
        var RoundedRectangle = EngineY.define(function (spec) {
            var spec = spec || {};

            this.color = spec.color || "#eee4da";
            this.alpha = spec.alpha || 1;
            this.width = spec.width || 107;
            this.height = spec.height || 107;
            this.radius = spec.radius || 6;
            this.repaint = function (context) {
                context.globalAlpha = this.alpha;
                context.fillStyle = this.color;
                context.roundRect(this.getGlobalX(), this.getGlobalY(), this.width, this.height, this.radius, true, false);
            };
        });

        /**
         * @param spec.font
         * @param spec.color
         * @param spec.textAlign
         * @param spec.textBaseline
         * @param spec.alpha
         */
        var Text = EngineY.define(function (spec) {
            var spec = spec || {};

            this.text = spec.text || "";
            this.alpha = spec.alpha || 1;
            this.font = (spec.weight ? spec.eight : "bold") + " " + (spec.size ? spec.size : 48) + "px Clear Sans, Helvetica Neue, Arial, sans-serif";
            this.color = spec.color || "#776e65";
            this.textAlign = spec.align || "center";
            this.textBaseline = spec.baseline || "middle";
            this.repaint = function (context) {
                context.font = this.font;
                context.textAlign = this.textAlign;
                context.textBaseline = this.textBaseline;
                context.fillStyle = this.color;
                context.globalAlpha = this.alpha;
                context.fillText(this.text, this.getGlobalX(), this.getGlobalY());
            }
        });

        var Tail = EngineY.define(function () {
            var color = {
                        "2": {color: "#776e65", background: "#eee4da"},
                        "4": {color: "#776e65", background: "#ede0c8"},
                        "8": {color: "#f9f6f2", background: "#f2b179"},
                        "16": {color: "#f9f6f2", background: "#f59563"},
                        "32": {color: "#f9f6f2", background: "#f67c5f"},
                        "64": {color: "#f9f6f2", background: "#f65e3b"},
                        "128": {color: "#f9f6f2", background: "#edcf72"},
                        "256": {color: "#f9f6f2", background: "#edcc61"},
                        "512": {color: "#f9f6f2", background: "#edc850"},
                        "1024": {color: "#f9f6f2", background: "#edc53f"},
                        "2048": {color: "#f9f6f2", background: "#edc22e"}
                    },
                    text = new Text({text: Math.random() > 0.5 ? "2" : "4", size: 55}),
                    background = new RoundedRectangle(),
                    changeColor = function () {
                        // 改变颜色
                        text.color = color[text.text].color;
                        background.color = color[text.text].background;
                    };

            changeColor();

            text.x = background.width / 2;
            text.y = background.height / 2;

            this.getValue = function () {
                return parseInt(text.text);
            };

            this.setValue = function (value) {
                text.text = value + "";
                changeColor();
            };

            this.addChild(background).addChild(text);
        });

        var NewGameButton = EngineY.define(function (spec) {
            var spec = spec || {};

            var text = new Text({text: "New Game", size: 18, color: "#f9f6f2"}),
                    backgrond = new RoundedRectangle({width: 129, height: 40, color: "#8f7a66"});
            text.x = backgrond.width / 2;
            text.y = backgrond.height / 2;

            this.x = spec.x || 0;
            this.y = spec.y || 0;
            this.width = backgrond.width;
            this.height = backgrond.height;

            this.onClick = function () {
            };

            this.addChild(backgrond).addChild(text);
        });

        /**
         * @param spec.text default "SCORE"
         * @param spec.x default 0
         * @param spec.y default 0
         */
        var Score = EngineY.define(function (spec) {
            var spec = spec || {};
            this.value = 0;

            var scoreLabel = new Text({text: this.value + "", size: 25, color: "#fff", baseline: "bottom"}),
                    descLabel = new Text({
                        text: spec.text ? spec.text : "SCORE",
                        size: 13,
                        color: "#eee4da",
                        baseline: "top"
                    }),
                    background = new RoundedRectangle({width: 112, height: 55, color: "#bbada0"});
            descLabel.x = scoreLabel.x = background.width / 2;
            descLabel.y = 10;
            scoreLabel.y = background.height - 2;

            this.x = spec.x || 0;
            this.y = spec.y || 0;

            this.setScore = function (score) {
                scoreLabel.text = score;
            };

            this.addChild(background).addChild(descLabel).addChild(scoreLabel);
        });

        var BodyBackground = EngineY.define(function (spec) {
            var spec = spec || {};

            this.x = spec.x || 0;
            this.y = spec.y || 0;
            this.gap = 15;
            this.width = 107;
            this.height = 107;
            this.radius = 6;
            this.backgroundColor = "#bbada0";
            this.rectangleColor = "#eee4da";
            this.repaint = function (context) {

                context.fillStyle = this.backgroundColor;
                context.roundRect(this.x, this.y,
                        this.gap * 5 + this.width * 4,
                        this.gap * 5 + this.height * 4,
                        this.radius, true, false);

                context.fillStyle = this.rectangleColor;
                context.globalAlpha = 0.35;
                for (var i = 0; i < 4; i++) {
                    for (var j = 0; j < 4; j++) {
                        context.roundRect(this.x + this.gap * (i + 1) + this.width * i,
                                this.y + this.gap * (j + 1) + this.height * j,
                                this.width, this.height, this.radius, true, false);
                    }
                }
            }
        });

        var Management = function (engine) {
            Management.STATUS_START = "start";
            Management.STATUS_PLAYING = "playing";

            this.engine = engine;

            this.tails = null;
            this.totalRows = 4;
            this.totalCols = 4;
            this.score = 0;
            this.bestScore = 0;
            this.life = 0;
            this.status = Management.STATUS_START;

            this.begin = function () {
                this.status = Management.STATUS_PLAYING;
                this.life = 16;
                this.score = 0;
                // 第一次运行时，this.tails == null
                if (this.tails === null) {
                    this.tails = [];
                } else {
                    this.clear();
                }

                this.generate();
                this.generate();

                return this;
            };

            this.generate = function () {
                this.life--;
                if (this.life == -1) {
                    this.over();
                    return;
                }
                var finish = true, x, y, tail;
                do {
                    x = Math.floor(Math.random() * 4);
                    y = Math.floor(Math.random() * 4);

                    if (this.tails[x * this.totalCols + y] == undefined) {
                        finish = false;
                        tail = new Tail();
                        tail.setPos(x, y);
                        this.tails[x * this.totalCols + y] = tail;
                        this.engine.add(tail);
                    }
                } while (finish);
            };

            this.remove = function (x, y) {
                this.life++;
                console.log("life: " + this.life);
                this.engine.remove(this.tails[x * this.totalCols + y]);
                this.tails[x * this.totalCols + y] = undefined;
            };

            this.clear = function () {
                this.life = 16;
                for (var i = 0; i < 4; i++) {
                    for (var j = 0; j < 4; j++) {
                        if (this.tails[i * this.totalCols + j] != undefined) {
                            this.remove(i, j);
                        }
                    }
                }
            };

            this.over = function () {
                this.status = Management.STATUS_START;
                alert("game is over");
            };

            this.move = function (x, y, targetX, targetY) {
                if (x != targetX || y != targetY) {
                    this.tails[targetX * this.totalCols + targetY] = this.tails[x * this.totalCols + y];
                    this.tails[targetX * this.totalCols + targetY].setPos(targetX, targetY);
                    this.tails[x * this.totalCols + y] = undefined;
                }
            };

            this.moveLeft = function () {
                for (var i = 0; i < 4; i++) {
                    for (var j = 0; j < 4; j++) {
                        if (this.tails[i * this.totalCols + j] !== undefined) {
                            this.tails[i * this.totalCols + j].moveLeft();
                        }
                    }
                }
                this.generate();
            };

            this.moveRight = function () {
                for (var i = 0; i < 4; i++) {
                    for (var j = 3; j >= 0; j--) {
                        if (this.tails[i * this.totalCols + j] !== undefined) {
                            this.tails[i * this.totalCols + j].moveRight();
                        }
                    }
                }
                this.generate();
            };

            this.moveUp = function () {
                for (var i = 0; i < 4; i++) {
                    for (var j = 0; j < 4; j++) {
                        if (this.tails[i + j * this.totalCols] !== undefined) {
                            this.tails[i + j * this.totalCols].moveUp();
                        }
                    }
                }
                this.generate();
            };

            this.moveDown = function () {
                for (var i = 0; i < 4; i++) {
                    for (var j = 3; j >= 0; j--) {
                        if (this.tails[i + j * this.totalCols] !== undefined) {
                            this.tails[i + j * this.totalCols].moveDown();
                        }
                    }
                }
                this.generate();
            };

            this.addPoint = function (point) {
                this.score += point;
                score.setScore(this.score + "");
            };

            /**=====================
             *  Tail game logic
             *=====================*/
            var management = this;

            Tail.prototype.coordinateX = 0;
            Tail.prototype.coordinateY = 0;
            Tail.prototype.originX = 0;
            Tail.prototype.originY = 170;
            /**
             * @param x [0 ~ 3]
             * @param y [0 ~ 3]
             */
            Tail.prototype.setPos = function (x, y) {
                this.coordinateX = x;
                this.coordinateY = y;
                this.y = this.originY + x * 107 + (x + 1) * 15;
                this.x = this.originX + y * 107 + (y + 1) * 15;
            };

            Tail.prototype.double = function () {
                // 添加分数
                management.addPoint(this.getValue());

                this.setValue(this.getValue() << 1);
            };

            Tail.prototype.moveLeft = function () {
                if (this.coordinateY != 0) {
                    var i = this.coordinateY - 1;
                    while (i >= 0) {
                        if (management.tails[this.coordinateX * management.totalCols + i] !== undefined) {
                            if (management.tails[this.coordinateX * management.totalCols + i].getValue() === this.getValue()) {
                                // 相同
                                management.remove(this.coordinateX, i);
                                this.double();
                                management.move(this.coordinateX, this.coordinateY, this.coordinateX, i);
                            } else {
                                // 不同
                                management.move(this.coordinateX, this.coordinateY, this.coordinateX, i + 1);
                            }
                            break;
                        }
                        i--;
                    }
                    if (i == -1) {
                        management.move(this.coordinateX, this.coordinateY, this.coordinateX, 0);
                    }
                }
            };
            Tail.prototype.moveRight = function () {
                if (this.coordinateY != 3) {
                    var i = this.coordinateY + 1;
                    while (i < 4) {
                        if (management.tails[this.coordinateX * management.totalCols + i] !== undefined) {
                            if (management.tails[this.coordinateX * management.totalCols + i].getValue() === this.getValue()) {
                                management.remove(this.coordinateX, i);
                                this.double();
                                management.move(this.coordinateX, this.coordinateY, this.coordinateX, i);
                            } else {
                                management.move(this.coordinateX, this.coordinateY, this.coordinateX, i - 1);
                            }
                            break;
                        }
                        i++;
                    }
                    if (i == 4) {
                        management.move(this.coordinateX, this.coordinateY, this.coordinateX, 3);
                    }
                }
            };
            Tail.prototype.moveUp = function () {
                if (this.coordinateX != 0) {
                    var i = this.coordinateX - 1;
                    while (i >= 0) {
                        if (management.tails[i * management.totalCols + this.coordinateY] !== undefined) {
                            if (management.tails[i * management.totalCols + this.coordinateY].getValue() === this.getValue()) {
                                management.remove(i, this.coordinateY);
                                this.double();
                                management.move(this.coordinateX, this.coordinateY, i, this.coordinateY);
                            } else {
                                management.move(this.coordinateX, this.coordinateY, i + 1, this.coordinateY);
                            }
                            break;
                        }
                        i--;
                    }
                    if (i == -1) {
                        management.move(this.coordinateX, this.coordinateY, 0, this.coordinateY);
                    }
                }
            };
            Tail.prototype.moveDown = function () {
                if (this.coordinateX != 3) {
                    var i = this.coordinateX + 1;
                    while (i < 4) {
                        if (management.tails[i * management.totalCols + this.coordinateY] !== undefined) {
                            if (management.tails[i * management.totalCols + this.coordinateY].getValue() === this.getValue()) {
                                management.remove(i, this.coordinateY);
                                this.double();
                                management.move(this.coordinateX, this.coordinateY, i, this.coordinateY);
                            } else {
                                management.move(this.coordinateX, this.coordinateY, i - 1, this.coordinateY);
                            }
                            break;
                        }
                        i++;
                    }
                    if (i == 4) {
                        management.move(this.coordinateX, this.coordinateY, 3, this.coordinateY);
                    }
                }
            };
        };

        var newGameButton = new NewGameButton({x: 374, y: 100}),
                score = new Score({x: 272, y: 15});

        var Panel = EngineY.define(function () {
            var text2048 = new Text({text: "2048", color: "#776e65", size: 80, align: "left", baseline: "top"}),
                    bestScore = new Score({text: "BEAT SCORE", x: 391, y: 15}),
                    bodyBackground = new BodyBackground({x: 0, y: 170});

            this.repaint = function (context) {
                context.fillStyle = "#faf8ef";
                context.fillRect(0, 0, context.canvas.width, context.canvas.height);
            };

            this.addChild(text2048)
                    .addChild(score)
                    .addChild(bestScore)
                    .addChild(newGameButton)
                    .addChild(bodyBackground);
        });

        var engine = EngineY(canvas),
                management = new Management(engine);

        engine.add(new Panel());

        newGameButton.onClick = function () {
            management.begin();
        };

        $(window).keypress(function (e) {
            if (management.status === Management.STATUS_PLAYING) {
                switch (e.charCode) {
                    // w key pass
                    case 119:
                        management.moveUp();
                        break;
                    // s key pass
                    case 115:
                        management.moveDown();
                        break;
                    // a key pass
                    case 97:
                        management.moveLeft();
                        break;
                    // d key pass
                    case 100:
                        management.moveRight();
                        break;
                }
            }
        });
    });
</script>
</body>
</html>